home *** CD-ROM | disk | FTP | other *** search
- /*
- * ptmid.c: Creates Protracker MODule files from MIDI files.
- * (My first attempt at Hungarian Notation.. wince!)
- *
- * Author: Andrew Scott (c)opyright 1994
- *
- * Date: 17/11/1993 ver 0.0
- * 8/1/1994 ver 0.1 - first release
- * 11/2/1994 ver 0.2 - added stats + fixed 15 handles limit
- * 29/6/1994 ver 0.3 - second release
- */
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <ctype.h>
- #include <io.h>
- #include <stdarg.h>
- #include <math.h>
- #include "ptmid.h"
- #include "samples.h"
-
- #ifndef R_OK
- #define R_OK 4
- #endif
-
- #if __MSDOS__
- #define SEPARATOR '\\'
- #else
- #define SEPARATOR '/'
- #endif
-
- /** Define HACKED_VERSION only if compiled under my Turbo C 2.0 variant **/
- #define HACKED_VERSION
-
- #define WTSP " \t\n"
-
- char bDrumch = 9, szId[5];
- int fNocopy = 0, fQuiet = 0, fExtend = 0, fStats = 0;
- int wRgmode = 0, wPatmax = 128, wMaxchan = 4, wQuantval = 16, wModfmt = 1;
- SI *rgpsiDrum[128], **rgppsiIns[129], *psiTree = NULL;
- Sz szTitle = "Converted by PTMID!", szQuant = NULL, szProgram;
- Fn fnSampath = {0};
-
- /*
- * Init: Does all those initialization things (which aren't too involved).
- */
- static void Init(void)
- {
- int i;
-
- #ifdef HACKED_VERSION
- void increase_handles(void);
-
- increase_handles(); /** Increase possible file handles to 40 **/
- #endif
-
- rgppsiIns[128] = NULL; /** Make sure sample-info arrays are clear **/
- for (i = 128; i--; ) {
- rgpsiDrum[i] = NULL;
- rgppsiIns[i] = NULL;
- }
- }
-
- void Error(Sz szMsg, ...)
- {
- va_list args;
-
- fprintf(stderr, "%s: ", szProgram);
- va_start(args, szMsg);
- vfprintf(stderr, szMsg, args);
- va_end(args);
- fprintf(stderr, "\n");
- }
-
- /*
- * BuildFn: Builds a full filename given a string which is the old filename,
- * and a default extension to use if one is not present in the string. After
- * building the new filename, any extension in the old name is removed.
- */
- void BuildFn(Fn fnNew, Sz fnOld, Sz fnExt)
- {
- Sz fnT = fnNew;
- int fExt = 0;
-
- while (*fnOld) {
- if ('.' == (*(fnT++) = *fnOld)) { /** Copy a char, test for extension **/
- fExt = 1;
- *fnOld = 0; /** yes.. note extension exists and remove it **/
- }
- fnOld++;
- }
- if (!fExt) { /** If no extension found **/
- *(fnT++) = '.';
- while ((*(fnT++) = *(fnExt++))); /** copy the default one - fnExt **/
- } else
- *fnT = 0;
- }
-
- /*
- * SzReadPfile: Reads the next string from the textfile given and returns it.
- * If file is at end, returns NULL.
- */
- Sz SzReadPfile(FILE *pfileText)
- {
- int ch, cch = 0, cchMac = 80;
- Sz szT, szStart;
-
- if (feof(pfileText))
- return NULL;
- szStart = szT = (Sz) malloc(80); /** Set aside 80 characters for line **/
- while ((ch = getc(pfileText)) != EOF && ch != '\n') {
- *szT = ch;
- if (++cch == cchMac) { /** If that's not enough **/
- cchMac += 40;
- szStart = (Sz) realloc(szStart, cchMac); /** increase in steps of 40 **/
- szT = szStart + cch;
- } else
- szT++;
- }
- *szT = 0;
- return (Sz) realloc(szStart, cch + 1);
- }
-
- /*
- * MakePsi: Given a filename and a place to store the new sample info,
- * will set up the defaults for it.
- *
- * date: 30/6/1994
- */
- void MakePsi(SI *psi, Sz fnSample)
- {
- psi->fnSample = strdup(fnSample);
- psi->pitch = -1;
- psi->perpitch = MIDDLEC;
- psi->freq = FreqGetFn(fnSample);
- psi->sample = -1;
- psi->bFinetune = 0;
- psi->psiL = psi->psiR = NULL;
- }
-
- /*
- * PsiAddsample: Given a sample's filename, will look it up in the tree
- * and return a pointer to it if it exists, else will create it and return
- * a pointer to the newly created entry.
- */
- SI *PsiAddsample(Sz fnSample)
- {
- SI *psiT;
-
- if (NULL == psiTree) { /** If nothing in tree **/
- psiT = psiTree = (SI *) malloc(sizeof(SI)); /** create root node **/
- MakePsi(psiT, fnSample);
- } else { /** Else **/
- SI *psiOld;
- int cmp;
-
- psiT = psiTree;
- while (psiT != NULL) { /** find spot for sample in tree **/
- psiOld = psiT;
- cmp = strcmp(psiT->fnSample, fnSample);
- if (!cmp)
- break;
- else if (0 > cmp)
- psiT = psiT->psiL;
- else
- psiT = psiT->psiR;
- }
- if (NULL == psiT) {
- psiT = (SI *) malloc(sizeof(SI)); /** and create entry **/
- if (0 > cmp)
- psiOld->psiL = psiT;
- else
- psiOld->psiR = psiT;
- MakePsi(psiT, fnSample);
- }
- }
- return psiT;
- }
-
- /*
- * PsiPrunePsi: Returns the given sample tree, but without any redundant
- * samples. Any redundant samples are freed. If no samples remain, NULL
- * is returned.
- */
- SI *PsiPrunePsi(SI *psi)
- {
- if (NULL == psi)
- return NULL;
- psi->psiL = PsiPrunePsi(psi->psiL); /** Prune left and right branches **/
- psi->psiR = PsiPrunePsi(psi->psiR);
- if (-1 == psi->pitch) { /** If root of tree redundant, need to remove it **/
- SI *psiT;
-
- if (NULL == psi->psiL) { /** If no left branch **/
- psiT = psi->psiR;
- free(psi); /** replace root **/
- psi = psiT; /** with right branch **/
- } else if (NULL == psi->psiR) { /** If no right branch **/
- psiT = psi->psiL;
- free(psi); /** replace root **/
- psi = psiT; /** with left branch **/
- } else if (NULL == psi->psiL->psiR) { /** If left branch has no right **/
- psiT = psi->psiL;
- psiT->psiR = psi->psiR; /** put right branch on right of left **/
- free(psi); /** and replace root **/
- psi = psiT; /** with left branch **/
- } else { /** Else.. there's 2 full branches - yuck! **/
- SI *psiOld;
-
- psiT = psi->psiL;
- while (NULL != psiT->psiR) { /** Find rightmost entry on left branch **/
- psiOld = psiT;
- psiT = psiT->psiR;
- }
- psiOld->psiR = psiT->psiL;
- psiT->psiL = psi->psiL;
- psiT->psiR = psi->psiR;
- free(psi); /** remove root **/
- psi = psiT; /** and replace it with that entry **/
- }
- }
- return psi;
- }
-
- /*
- * PitchConv: The supplied token is converted to the equivalent MIDI
- * pitch value, eg. C2 = 60, C#2 = 61. -1 is returned if not a note.
- *
- * date: 30/6/1994
- */
- int PitchConv(Sz szTok)
- {
- static int rgbPitch[7] = {45, 47, 36, 38, 40, 41, 43};
- int irgb;
-
- if ((irgb = toupper(szTok[0]) - 'A') < 0 || 6 < irgb)
- return -1;
- else if ('#' == szTok[1])
- szTok++;
- return rgbPitch[irgb] + 12 * atoi(szTok + 1) +
- ('#' == szTok[0] ? 1 : 0);
- }
-
- /*
- * ReadconfigFn: Given the filename of the configuration file, it interprets
- * each line and sets up options and sample-tables.
- *
- * date: 30/6/1994 - added ditto, sample paths, sample frequencies
- * 1/7/1994 - added finetuning for those odd frequencies
- */
- void ReadconfigFn(Sz fnConfig)
- {
- FILE *pfileConfig;
- Sz szLine, szTok;
- int csz = 0, fError = 0, irgppsiLast = 0;
- static Sz rgszIds[] = {
- "M.K.",
- "TDZ1", "TDZ2", "TDZ3", "M!K!", "5CHN", "6CHN", "7CHN", "8CHN",
- "9CHN", "10CH", "11CH", "12CH", "13CH", "14CH", "15CH", "16CH",
- "17CH", "18CH", "19CH", "20CH", "21CH", "22CH", "23CH", "24CH",
- "25CH", "26CH", "27CH", "28CH", "29CH", "30CH", "31CH", "32CH"
- };
-
- if (NULL == (pfileConfig = fopen(fnConfig, "rt"))) {
- Error("Cannot find config file: %s", fnConfig);
- exit(1);
- }
- while ((szLine = SzReadPfile(pfileConfig)) != NULL) { /** With every line.. **/
- csz++;
- if ('#' != szLine[0] && NULL != (szTok = strtok(szLine, WTSP)))
- if ('0' <= szTok[0] && '9' >= szTok[0] || !strcmp(szTok, "def")) {
- int irgppsi, cpsi; /** If an instrument definition **/
- SI **ppsi;
-
- if ('d' == szTok[0])
- irgppsi = 128;
- else
- irgppsi = atoi(szTok); /** decode instrument **/
- if (irgppsi < 129) {
- szTok = strtok(NULL, WTSP);
- if (!strcmp(szTok, "\"")) /** Check for ditto **/
- if (NULL == rgppsiIns[irgppsi])
- rgppsiIns[irgppsi] = rgppsiIns[irgppsiLast]; /** Yes.. copy **/
- else
- fError = 1;
- else { /** No.. **/
- irgppsiLast = irgppsi;
- while (NULL != szTok) { /*** With every sample.. ***/
- if (NULL == rgppsiIns[irgppsi]) /*** Ensure allocated ***/
- rgppsiIns[irgppsi] = ppsi = (SI **) malloc(sizeof(SI *) * 2);
- else {
- ppsi = rgppsiIns[irgppsi];
- for (cpsi = 2; NULL != *ppsi; cpsi++, ppsi++);
- rgppsiIns[irgppsi] = ppsi = (SI **) realloc(rgppsiIns[irgppsi],
- sizeof(SI *) * cpsi);
- ppsi += cpsi - 2;
- }
- ppsi[0] = PsiAddsample(szTok); /*** Put sample in array ***/
- ppsi[1] = NULL;
- szTok = strtok(NULL, WTSP);
- }
- }
- } else
- fError = 1;
-
- } else if ('d' == szTok[0] && '0' <= szTok[1] && '9' >= szTok[1]) {
- int irgpsi, tPitch; /** If a percussion definition **/
-
- if ((irgpsi = atoi(szTok + 1)) < 128 && /** decode instrument **/
- (szTok = strtok(NULL, WTSP)) != NULL) {
- if (NULL != rgpsiDrum[irgpsi])
- free(rgpsiDrum[irgpsi]); /** and free up if previously used **/
- rgpsiDrum[irgpsi] = PsiAddsample(szTok); /** Put sample in array **/
- if ((szTok = strtok(NULL, WTSP)) != NULL &&
- (tPitch = PitchConv(szTok)) != -1) /** If play pitch there **/
- rgpsiDrum[irgpsi]->perpitch = tPitch; /** store it **/
- } else
- fError = 1;
-
- } else if (!strcmp(szTok, "spath")) { /** If sample path **/
- if ((szTok = strtok(NULL, WTSP)) == NULL)
- fError = 1;
- else
- strncpy(fnSampath, szTok, MAXFILELEN); /** store it **/
-
- } else if (!strcmp(szTok, "sample")) { /** If sample info **/
- Sz fnSample;
- SI *psi;
- int tPitch;
-
- if ((fnSample = strtok(NULL, WTSP)) == NULL) /** Get name **/
- fError = 1;
- else if ((szTok = strtok(NULL, WTSP)) == NULL)
- fError = 1;
- else if ((tPitch = PitchConv(szTok)) == -1) /** Get pitch **/
- fError = 1;
- else {
- double scale;
-
- psi = PsiAddsample(fnSample); /** Make sure sample allocated **/
- if (C2FREQUENCY == psi->freq)
- scale = 0;
- else /** Calculate any scaling for special samples **/
- scale = -12 * log(psi->freq / 8287.1369) / log(2.0);
- psi->pitch = tPitch + (int) scale;
- psi->bFinetune = (int) (8 * modf(scale, NULL));
- if ((szTok = strtok(NULL, WTSP)) == NULL)
- psi->wLppos = 0;
- else
- psi->wLppos = atoi(szTok); /** Get loop start **/
- if ((szTok = strtok(NULL, WTSP)) == NULL)
- psi->wLplen = 0;
- else
- psi->wLplen = atoi(szTok); /** Get loop length **/
- }
-
- } else if (!strcmp(szTok, "modfmt")) /** If module-id **/
- if ((szTok = strtok(NULL, WTSP)) == NULL)
- fError = 1;
- else {
- int wT;
-
- if ((wT = atoi(szTok)) == 1 || 2 == wT)
- wModfmt = wT;
- else
- fError = 1;
- }
-
- else if (!strcmp(szTok, "maxchan")) /** If max. channels **/
- if ((szTok = strtok(NULL, WTSP)) == NULL)
- fError = 1;
- else {
- int wT;
-
- if ((wT = atoi(szTok)) > 0 && MAXCHANS >= wT)
- wMaxchan = wT; /** store them **/
- else
- fError = 1;
- }
-
- else if (!strcmp(szTok, "drumch")) /** If MIDI percussion channel **/
- if ((szTok = strtok(NULL, WTSP)) == NULL)
- fError = 1;
- else {
- int bT;
-
- if ((bT = atoi(szTok)) != 0)
- bDrumch = bT - 1; /** store it **/
- }
-
- else if (!strcmp(szTok, "rgmode")) /** If range-mode option **/
- if ((szTok = strtok(NULL, WTSP)) == NULL)
- fError = 1;
- else {
- int wT;
-
- if ((wT = atoi(szTok)) >= 0 || 2 >= wT)
- wRgmode = wT; /** store it **/
- }
-
- else if (!strcmp(szTok, "fract") && NULL == szQuant) { /** If quantize frac. **/
- int wT;
-
- if ((szQuant = strtok(NULL, WTSP)) == NULL ||
- !(wT = ValidquantSz(szQuant))) /** decode **/
- fError = 1;
- else
- wQuantval = wT; /** and store it **/
-
- } else if (!strcmp(szTok, "extend")) /** If extend-notes flag **/
- fExtend = 1; /** toggle **/
- else if (!strcmp(szTok, "nocopy")) /** If no-copyright flag **/
- fNocopy = 1; /** toggle **/
- else
- fError = 1;
-
- if (fError) { /** If an error at any point, reveal line **/
- Error("Error in config file: line %d", csz);
- exit(1); /** and quit **/
- }
- free(szLine);
- }
-
- if (NULL == rgppsiIns[128]) {
- Error("No default instrument defined in config file");
- exit(1);
- }
- if ((psiTree = PsiPrunePsi(psiTree)) == NULL) {
- Error("No sample definitions found in config file");
- exit(1);
- }
- if (1 == wModfmt) /** If Protracker format used **/
- strncpy(szId, rgszIds[wMaxchan], 5); /** store special Id **/
- }
-
- /*
- * main: Parses arguments to program and opens appropriate MOD and MID files.
- */
- int main(int argc, char **argv)
- {
- int cNames = 0;
- Sz fnDef, fnConfig = DEFCONFIG;
- Fn fnIn, fnOut;
- FILE *pfileMod;
- Tune *ptuneMusic;
-
- Init();
- if ((szProgram = strrchr(*argv, SEPARATOR)) != NULL)
- szProgram++;
- else
- szProgram = *argv;
- for (argv++; NULL != *argv; argv++) /** Run through all parameters **/
- if ('-' == **argv) /** If parameter is a switch **/
- switch ((*argv)[1]) { /** check what sort **/
- case 'c':
- if ((*argv)[2])
- fnConfig = *argv + 2; /** c: set config file **/
- break;
- case 'd':
- bDrumch = atoi(*argv + 2) - 1; /** d: set drum channel **/
- break;
- case 'f': {
- int wT; /** f: set quantize fraction **/
-
- if ((wT = ValidquantSz(*argv + 2)))
- wQuantval = wT;
- else
- Error("Invalid quantize fraction - using default");
- break;
- }
- case 'q':
- fQuiet = !fQuiet; /** q: toggle quiet mode **/
- break;
- case 's':
- fStats = !fStats; /** s: toggle statistics mode **/
- break;
- }
- else { /** Else must be a filename **/
- if (0 == cNames)
- BuildFn(fnIn, fnDef = *argv, "mid");
- else if (1 == cNames)
- fnDef = *argv;
- cNames++;
- }
-
- if (1 > cNames || 2 < cNames) { /** If no filenames - error **/
- printf("Use: ptmid [-cFile] [-dChannel] [-fFrac] [-q] [-s] infile[.mid] [outfile[.mod]]\n");
- printf(" version " PTVER "\n");
- printf(" Creates Protracker MOD and MTM files from General MIDI files\n");
- exit(1);
- }
-
- ReadconfigFn(fnConfig);
- /** Ensure output filename has correct extension if not supplied **/
- BuildFn(fnOut, fnDef, (1 == wModfmt) ? "mod" : "mtm");
- if (!fQuiet)
- printf("ptmid ver " PTVER ": Converting '%s' to '%s'\n", fnIn, fnOut);
-
- if (access(fnIn, R_OK)) {
- ERROR;
- exit(1);
- }
- if ((ptuneMusic = PtuneLoadFn(fnIn)) == NULL) {
- Error("Not a legal MIDI file: %s", fnIn);
- exit(1);
- }
-
- if (!fQuiet)
- printf("Analyzing..\n");
- ResolvePtune(ptuneMusic);
-
- if (!fQuiet)
- printf("Writing..\n");
- if ((pfileMod = fopen(fnOut, "wb")) == NULL) {
- ERROR;
- exit(1);
- }
- SavePtunePfile(ptuneMusic, pfileMod);
- fclose(pfileMod);
-
- if (!fQuiet)
- printf("Done.\n");
- return 0;
- }
-